import { Value } from "../../inner/Value";
import Dropdown from "../../Dropdown";
import { Menu } from "./Menu";
import { CascaderStore } from "./store";
import { Option } from "./Option";
import type { TagGroupProps } from "../../TagGroup";
import { computed, defineComponent, PropType, provide, ref, VNode, watchEffect } from "vue";
import { TreeCheckMod } from "../../inner/Constaint";
import Empty from "../../Empty";

export interface CascaderProps {
    disabled?: boolean,
    clearable?: boolean,
    size?: 'small'|'large',
    prepend?: string | VNode,
    modelValue?: string | string[] | number[],
    valueField?: string,
    titleField?: string,
    mode?: TreeCheckMod,
    showMax?: TagGroupProps['max'],
    max?: number
    showMore?: boolean,
    filter?: boolean,
    emptyText?: string,
    seperator?: string,
    transfer?: boolean,
    header?: VNode,
    footer?: VNode,
    triggerRender?: (labels: any, values: any) => VNode
    align?: 'bottomLeft'|'bottomRight',
    revers?: boolean,
    data: any[],
    trigger?:'click'|'hover',
    multi?: boolean,
    changeOnSelect?: boolean,
    placeholder?: string,
    beforeChecked?: (item: any, checked: boolean) => boolean,
    tagRender?: (item: any) => string | number | VNode,
    loadData?: (item: any) => Promise<any>
    asFormField?: boolean
}

export const CascaderContextKey = Symbol('CascaderContextKey');

export default defineComponent({
    name: 'Cascader',
    props: {
        disabled: {type: Boolean as PropType<CascaderProps['disabled']>},
        clearable: {type: Boolean as PropType<CascaderProps['clearable']>},
        size: {type: String as PropType<CascaderProps['size']>},
        prepend: {type: [String, Object] as PropType<CascaderProps['prepend']>},
        modelValue: {type: [String, Array] as PropType<CascaderProps['modelValue']>},
        valueField: {type: String as PropType<CascaderProps['valueField']>},
        titleField: {type: String as PropType<CascaderProps['titleField']>},
        mode: {type: String as PropType<CascaderProps['mode']>},
        showMax: {type: Number as PropType<CascaderProps['showMax']>},
        max: {type: Number as PropType<CascaderProps['max']>},
        showMore: {type: Boolean as PropType<CascaderProps['showMore']>},
        filter: {type: Boolean as PropType<CascaderProps['filter']>},
        emptyText: {type: String as PropType<CascaderProps['emptyText']>},
        seperator: {type: String as PropType<CascaderProps['seperator']>},
        transfer: {type: Boolean as PropType<CascaderProps['transfer']>, default: true},
        header: {type: Object as PropType<CascaderProps['header']>},
        footer: {type: Object as PropType<CascaderProps['footer']>},
        triggerRender: {type: Function as PropType<CascaderProps['triggerRender']>},
        align: {type: String as PropType<CascaderProps['align']>},
        revers: {type: Boolean as PropType<CascaderProps['revers']>, default: true},
        data: {type: Array as PropType<CascaderProps['data']>},
        trigger: {type: String as PropType<CascaderProps['trigger']>},
        multi: {type: Boolean as PropType<CascaderProps['multi']>},
        tagRender: {type: Function as PropType<CascaderProps['tagRender']>},
        changeOnSelect: {type: Boolean as PropType<CascaderProps['changeOnSelect']>},
        placeholder: {type: String as PropType<CascaderProps['placeholder']>},
        beforeChecked: {type: Function as PropType<CascaderProps['beforeChecked']>},
        loadData: {type: Function as PropType<CascaderProps['loadData']>},
        asFormField: {type: Boolean as PropType<CascaderProps['asFormField']>, default: true},
    },
    emits: ['change', 'exceed', 'update:modelValue', 'select'],
    setup (props: CascaderProps, {emit}) {
        const store = new CascaderStore(props, emit);
        const visible = ref(false);
        const query = ref<string>('');
        const trigger = props.trigger ?? 'click';
        const emptyText = props.emptyText ?? '暂无数据';
        const filterWrap = ref();
        const allTextNodes: Node[] = [];

        const titleField = props.titleField ?? 'title';
        const seperator = props.seperator ?? '/';
        const align = props.align ?? 'bottomLeft';
        const classList = computed(() => ({
            'cm-cascader': true,
            'cm-cascader-disabled': props.disabled,
            'cm-cascader-clearable': !props.disabled && props.clearable && store.value.value && store.value.value.length,
            [`cm-cascader-${props.size}`]: props.size
        }));

        const text = computed(() => {
            const vals = store.value.value;
            const newVals = vals?.filter(val => store.store.nodeMap[val]);
            const arr = newVals ? newVals.map((val: any) => {
                const item = store.store.nodeMap[val];
                return props.multi ? item : item[titleField];
            }) : [];
            return props.multi ? arr : (arr.length ? arr.join(seperator) : '');
        });

        const onSelect = (item: any) => {
        // 点击的是叶子节点或者设置点击即改变
            if (!props.multi) {
                if (!(item.children && item.children.length) || props.changeOnSelect) {
                    emit('select', item);
                    const vals = store.selectedKey.value;
                    const rets = [...vals];
                    store.value.value = rets;
                    if (props.filter) {
                        query.value = '';
                    }
                    emit('change', rets);
                }
            }
            // 点击叶子节点进行关闭
            if (!(item.children && item.children.length) && !props.multi) {
                visible.value = false;
            }
        };

        const onClear = () => {
            store.value.value = [];
            emit('change', []);
        };

        // 过滤查询
        watchEffect(() => {
            const queryStr = query.value;
            if (queryStr) {
                store.filter(queryStr);

                // 高亮搜索字符
                queueMicrotask(() => {
                    buildNodes();
                    hilightKeyword(queryStr);
                });
            }
        });

        // 高亮关键字
        const hilightKeyword = (queryStr: string) => {
        // 不支持高亮则返回
            if (!CSS.highlights) {
                return;
            }

            CSS.highlights.delete('cm-search-results');

            const str = queryStr.trim().toLowerCase();
            if (!str) {
                return;
            }

            const ranges = allTextNodes
                .map((el) => {
                    return { el, text: el.textContent?.toLowerCase() };
                })
                .map(({ text, el }) => {
                    const indices = [];
                    let startPos = 0;
                    while (text && startPos < text.length) {
                        const index = text.indexOf(str, startPos);
                        if (index === -1) break;
                        indices.push(index);
                        startPos = index + str.length;
                    }
                    return indices.map((index) => {
                        const range = new Range();
                        range.setStart(el, index);
                        range.setEnd(el, index + str.length);
                        return range;
                    });
                });

            const searchResultsHighlight = new Highlight(...ranges.flat());
            CSS.highlights.set('cm-search-results', searchResultsHighlight);
        };

        // 撤消按键，删除最后一个value
        const onDeleteLastValue = () => {
            if (props.multi) {
                const val = store.value.value;
                if (val.length > 0) {
                    const key = val.pop();
                    store.checkNode(key!, false);
                }
            }
        };

        const clearQuery = () => {
            query.value = '';
        };

        /**
     * 构建搜索的节点
     * @returns
     */
        const buildNodes = () => {
        // 不支持高亮则返回
            if (!CSS.highlights) {
                return;
            }

            const treeWalker = document.createTreeWalker(filterWrap.value, NodeFilter.SHOW_TEXT);
            let currentNode = treeWalker.nextNode();
            while (currentNode) {
                allTextNodes.push(currentNode);
                currentNode = treeWalker.nextNode();
            }
        };

        provide(CascaderContextKey, {
            onSelect,
            loadData: props.loadData,
            multi: props.multi,
            clearQuery
        });

        return () => <div class={classList.value} tab-index="0">
            <Dropdown v-model={visible.value} transfer={props.transfer} align={align} revers={props.revers}
                trigger="click" disabled={props.disabled} menu={
                    <div class="cm-cascader-dropdown">
                        {
                            props.header ?? null
                        }
                        {
                            query.value
                                ? <div class="cm-cascader-wrap" ref={filterWrap}>
                                    <div class={{'cm-cascader-list': true, 'cm-cascader-list-empty': !store.store.filteredList?.length}}>
                                        {
                                            store.store.filteredList?.length
                                                ?
                                                store.store.filteredList.map(item => {
                                                    return <Option filter={props.filter} store={store} data={item} seperator={seperator}/>;
                                                })
                                                : <div class="cm-cascader-empty">
                                                    <Empty width={100} text={emptyText}/>
                                                </div>
                                        }
                                    </div>
                                </div>
                                : <div class="cm-cascader-wrap">
                                    {
                                        store.store.columns.map((column: any[], index: number) => {
                                            return <Menu key={store.selectedKey.value[index - 1] || 'root'} data={column} trigger={trigger}
                                                store={store} emptyText={props.emptyText}/>;
                                        })
                                    }
                                </div>
                        }
                        {
                            props.footer ?? null
                        }
                    </div>
                }>
                {
                    props.triggerRender
                        ? <span class="cm-cascader-trigger">{props.triggerRender?.(text.value, store.value.value)}</span>
                        : <Value prepend={props.prepend} text={text.value} showMore={props.showMore} showMax={props.showMax} onClear={onClear} clearable={props.clearable}
                            placeholder={props.placeholder} disabled={props.disabled} size={props.size} multi={props.multi}
                            query={query} filter={props.filter} onDeleteLastValue={onDeleteLastValue}
                            tagRender={props.tagRender}/>
                }
            </Dropdown>
        </div>;
    }
});
